package net.xconcepts.stockdata.model;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import net.xconcepts.stockdata.model.analysis.SymbolFilter;
import net.xconcepts.stockdata.model.analysis.symbol.Symbol;
import net.xconcepts.stockdata.model.tick.SymbolTick;
import net.xconcepts.stockdata.model.tick.TickDataField;


public class Trader {

	private final Portfolio mPortfolio;
	private final Collection<SymbolFilter> mBuyFilters = new HashSet<SymbolFilter>();
	private final Collection<SymbolFilter> mSellFilters = new HashSet<SymbolFilter>();
	private final Prioritizer mPrioritizer;
	private double mTransactionCost = 0;
	
	/**
	 * 
	 * @param portfolio
	 * @param buyFilter
	 */
	public Trader(Portfolio portfolio, SymbolFilter buyFilter, SymbolFilter sellFilter, Prioritizer prioritizer) {
		mPortfolio = portfolio;
		mBuyFilters.add(buyFilter);
		mSellFilters.add(sellFilter);
		mPrioritizer = prioritizer;
	}
	
	/**
	 * 
	 * @param portfolio
	 * @param marketFilters Multiple filters are treated as OR, use multiple rules in
	 * single filter to get AND functionality
	 */
	public Trader(Portfolio portfolio, Collection<SymbolFilter> marketFilters,
			Prioritizer prioritizer) {
		mPortfolio = portfolio;
		mBuyFilters.addAll(marketFilters);
		mPrioritizer = prioritizer;
	}
	
	/**
	 * @return portfolio value
	 * @param marketTicks
	 */
	public void processMarketTick(Map<Symbol, SymbolTick> marketTicks) {
		Map<Symbol, SymbolTick> buyTicks = new HashMap<Symbol, SymbolTick>();
		
		for(SymbolFilter marketFilter : mBuyFilters) {
			buyTicks.putAll(marketFilter.processMarketTick(marketTicks));
		}
		
		Map<Symbol, SymbolTick> ownedTicks = getOwnedSymbolTicks(marketTicks);
		
		Map<Symbol, SymbolTick> sellTicks = new HashMap<Symbol, SymbolTick>();
		
		for(SymbolFilter marketFilter : mSellFilters) {
			sellTicks.putAll(marketFilter.processMarketTick(ownedTicks));
		}
		
		List<TraderAction> actions = mPrioritizer.processMarketTick(buyTicks, sellTicks);
		executeActions(actions);
		
	}
	
	/**
	 * 
	 * @param marketTicks
	 * @return  null if not enough value to calculate
	 */
	public final Double getPortfolioValue(Map<Symbol, SymbolTick> marketTicks) {
		Double value = mPortfolio.getCash();
		
		for(Symbol symbol : mPortfolio.getOwnedSymbols()) {
			if(mPortfolio.getOwnedSymbols().size() > 0 && (marketTicks.containsKey(symbol))) {
				int numShares =  mPortfolio.getNumShares(symbol);
				double price = marketTicks.get(symbol).getData(TickDataField.Close);
				value += numShares * price;
			} else {
				value = null;
				break;
			}
		}
		
		return value;
	}
	
	/**
	 * Executes actions in the order listed. If an action cannot be executed,
	 * an exception is thrown
	 * @param actions
	 */
	protected final void executeActions(List<TraderAction> actions) {
		for(TraderAction action : actions) {
			executeAction(action);
		}
	}
	
	protected final void executeAction(TraderAction action) {
		if(action.action.equals(TraderAction.Action.BUY)) {
			if((action.amount * action.marketPrice) > mPortfolio.getCash()) return;
				//throw new IllegalArgumentException("Insufficient Funds");
			
			mPortfolio.addShares(action.symbol, action.amount);
			mPortfolio.addCash(-1 * action.amount * action.marketPrice);
			
			
		} else if (action.action.equals(TraderAction.Action.SELL)) {
			if(action.amount > mPortfolio.getNumShares(action.symbol)) return;
				//throw new IllegalArgumentException("Insufficient Shares");
			
			mPortfolio.addShares(action.symbol, -1 * action.amount);
			mPortfolio.addCash(action.amount * action.marketPrice);
			
		} else {
			throw new IllegalArgumentException("Invalid Action Type");
		}
		
		mPortfolio.addCash(-1 * mTransactionCost);
		onTradeExecute(action);
	}
	
	public void onTradeExecute(TraderAction action) {
		System.out.println(action.toString());
	}
	
	public Map<Symbol, SymbolTick> getOwnedSymbolTicks(Map<Symbol, SymbolTick> marketTicks) {
		Map<Symbol, SymbolTick> ownedTicks = new HashMap<Symbol, SymbolTick>();
		
		for (Symbol sym : mPortfolio.getOwnedSymbols()) {
			ownedTicks.put(sym, marketTicks.get(sym));
		}
		
		return ownedTicks;
	}
	
	public void setTransactionCost(double cost) {
		mTransactionCost = cost;
	}
	
	public double getTransactionCost() {
		return mTransactionCost;
	}
}
